Plotly allows you to create over 40 beautiful interactive web-based visualizations that can be displayed in Jupyter notebooks or saved to HTML files. It is widely used to plot scientific, statistical and financial data. Plotly is free to use unless you want them to host your data for you.
You can install using Anaconda under the environment tab by searching for Plotly. You'll also need Cufflinks and a few other packages that you can install by running : conda install -c conda-forge cufflinks-py in your command line or terminal. Also you can use the commands pip install plotly and pip install cufflinks. Cufflinks connects Plotly to Pandas.
import pandas as pd
import numpy as np
import chart_studio.plotly as py
import cufflinks as cf
import seaborn as sns
import plotly.express as px
%matplotlib inline
# Make Plotly work in your Jupyter Notebook
from plotly.offline import download_plotlyjs, init_notebook_mode, plot, iplot
init_notebook_mode(connected=True)
# Use Plotly locally
cf.go_offline()
# Create a dataframe using a NumPy array that is 50 by 4
arr_1 = np.random.randn(50, 4)
df_1 = pd.DataFrame(arr_1, columns=['A','B','C','D'])
df_1.head()
A | B | C | D | |
---|---|---|---|---|
0 | -0.629896 | -1.029932 | 1.978985 | 0.735458 |
1 | -1.603906 | -0.544156 | 0.253062 | 1.342460 |
2 | 0.605409 | 1.266526 | -0.795807 | -0.784913 |
3 | 1.049247 | -0.147862 | -0.086077 | 0.611336 |
4 | -0.650904 | -1.613309 | -0.635976 | -1.069726 |
# Compare old plots to a Plotly interactive plot
# You can save as PNG, Zoom, Pan, Turn off & on Data and more
df_1.plot()
<AxesSubplot:>
df_1.iplot()
# Allows us to create graph objects for making more customized plots
import plotly.graph_objects as go
# Use included Google price data to make one plot
df_stocks = px.data.stocks()
df_stocks.head()
date | GOOG | AAPL | AMZN | FB | NFLX | MSFT | |
---|---|---|---|---|---|---|---|
0 | 2018-01-01 | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 |
1 | 2018-01-08 | 1.018172 | 1.011943 | 1.061881 | 0.959968 | 1.053526 | 1.015988 |
2 | 2018-01-15 | 1.032008 | 1.019771 | 1.053240 | 0.970243 | 1.049860 | 1.020524 |
3 | 2018-01-22 | 1.066783 | 0.980057 | 1.140676 | 1.016858 | 1.307681 | 1.066561 |
4 | 2018-01-29 | 1.008773 | 0.917143 | 1.163374 | 1.018357 | 1.273537 | 1.040708 |
px.line(df_stocks, x='date', y='GOOG', labels={'x':'Date', 'y':'Price'})
# Make multiple line plots
px.line(df_stocks, x='date', y=['GOOG','AAPL'], labels={'x':'Date', 'y':'Price'},
title='Apple Vs. Google')
# Create a figure to which I'll add plots
fig = go.Figure()
# You can pull individual columns of data from the dataset and use markers or not
fig.add_trace(go.Scatter(x=df_stocks.date, y=df_stocks.AAPL,
mode='lines', name='Apple'))
fig.add_trace(go.Scatter(x=df_stocks.date, y=df_stocks.AMZN,
mode='lines+markers', name='Amazon'))
# You can create custom lines (Dashes : dash, dot, dashdot)
fig.add_trace(go.Scatter(x=df_stocks.date, y=df_stocks.GOOG,
mode='lines+markers', name='Google',
line=dict(color='firebrick', width=2, dash='dashdot')))
# Further style the figure
# fig.update_layout(title='Stock Price Data 2018 - 2020',
# xaxis_title='Price', yaxis_title='Date')
# Go crazy styling the figure
fig.update_layout(
# Shows gray line without grid, styling fonts, linewidths and more
xaxis=dict(
showline=True,
showgrid=False,
showticklabels=True,
linecolor='rgb(204, 204, 204)',
linewidth=2,
ticks='outside',
tickfont=dict(
family='Arial',
size=12,
color='rgb(82, 82, 82)',
),
),
# Turn off everything on y axis
yaxis=dict(
showgrid=False,
zeroline=False,
showline=False,
showticklabels=False,
),
autosize=False,
margin=dict(
autoexpand=False,
l=100,
r=20,
t=110,
),
showlegend=False,
plot_bgcolor='white'
)
fig
# Get population change in US by querying for US data
df_us = px.data.gapminder().query("country == 'United States'")
df_us.head()
country | continent | year | lifeExp | pop | gdpPercap | iso_alpha | iso_num | |
---|---|---|---|---|---|---|---|---|
1608 | United States | Americas | 1952 | 68.44 | 157553000 | 13990.48208 | USA | 840 |
1609 | United States | Americas | 1957 | 69.49 | 171984000 | 14847.12712 | USA | 840 |
1610 | United States | Americas | 1962 | 70.21 | 186538000 | 16173.14586 | USA | 840 |
1611 | United States | Americas | 1967 | 70.76 | 198712000 | 19530.36557 | USA | 840 |
1612 | United States | Americas | 1972 | 71.34 | 209896000 | 21806.03594 | USA | 840 |
px.bar(df_us, x='year', y='pop')
# Create a stacked bar with more customization
df_tips = px.data.tips()
px.bar(df_tips, x='day', y='tip', color='sex', title='Tips by Sex on Each Day',
labels={'tip': 'Tip Amount', 'day': 'Day of the Week'})
# Place bars next to each other
px.bar(df_tips, x="sex", y="total_bill",
color='smoker', barmode='group')
# Display pop data for countries in Europe in 2007 greater than 2000000
df_europe = px.data.gapminder().query("continent == 'Europe' and year == 2007 and pop > 2.e6")
fig = px.bar(df_europe, y='pop', x='country', text='pop', color='country')
# Put bar total value above bars with 2 values of precision
fig.update_traces(texttemplate='%{text:.2s}', textposition='outside')
# Set fontsize and uniformtext_mode='hide' says to hide the text if it won't fit
fig.update_layout(uniformtext_minsize=8)
# Rotate labels 45 degrees
fig.update_layout(xaxis_tickangle=-45)
# Use included Iris data set
df_iris = px.data.iris()
df_iris.head()
sepal_length | sepal_width | petal_length | petal_width | species | species_id | |
---|---|---|---|---|---|---|
0 | 5.1 | 3.5 | 1.4 | 0.2 | setosa | 1 |
1 | 4.9 | 3.0 | 1.4 | 0.2 | setosa | 1 |
2 | 4.7 | 3.2 | 1.3 | 0.2 | setosa | 1 |
3 | 4.6 | 3.1 | 1.5 | 0.2 | setosa | 1 |
4 | 5.0 | 3.6 | 1.4 | 0.2 | setosa | 1 |
# Create a scatter plot by defining x, y, different color for count of provided
# column, size based on supplied column and additional data to display on hover
px.scatter(df_iris, x="sepal_width", y="sepal_length", color="species",
size='petal_length', hover_data=['petal_width'])
# Create a customized scatter with black marker edges with line width 2, opaque
# and colored based on width. Also show a scale on the right
fig = go.Figure()
fig.add_trace(go.Scatter(
x=df_iris.sepal_width, y=df_iris.sepal_length,
mode='markers',
marker_color=df_iris.sepal_width,
text=df_iris.species,
marker=dict(showscale=True)
))
fig.update_traces(marker_line_width=2, marker_size=10)
# Working with a lot of data use Scattergl
fig = go.Figure(data=go.Scattergl(
x = np.random.randn(100000),
y = np.random.randn(100000),
mode='markers',
marker=dict(
color=np.random.randn(100000),
colorscale='Viridis',
line_width=1
)
))
fig
# Create Pie chart of the largest nations in Asia
# Color maps here plotly.com/python/builtin-colorscales/
df_samer = px.data.gapminder().query("year == 2007").query("continent == 'Asia'")
df_samer.head()
country | continent | year | lifeExp | pop | gdpPercap | iso_alpha | iso_num | |
---|---|---|---|---|---|---|---|---|
11 | Afghanistan | Asia | 2007 | 43.828 | 31889923 | 974.580338 | AFG | 4 |
95 | Bahrain | Asia | 2007 | 75.635 | 708573 | 29796.048340 | BHR | 48 |
107 | Bangladesh | Asia | 2007 | 64.062 | 150448339 | 1391.253792 | BGD | 50 |
227 | Cambodia | Asia | 2007 | 59.723 | 14131858 | 1713.778686 | KHM | 116 |
299 | China | Asia | 2007 | 72.961 | 1318683096 | 4959.114854 | CHN | 156 |
px.pie(df_samer, values='pop', names='country',
title='Population of Asian continent',
color_discrete_sequence=px.colors.sequential.RdBu)
# Customize pie chart
colors = ['blue', 'green', 'black', 'purple', 'red', 'brown']
fig = go.Figure(data=[go.Pie(labels=['Water','Grass','Normal','Psychic', 'Fire', 'Ground'],
values=[110,90,80,80,70,60])])
# Define hover info, text size, pull amount for each pie slice, and stroke
fig.update_traces(hoverinfo='label+percent', textfont_size=20,
textinfo='label+percent', pull=[0.1, 0, 0.2, 0, 0, 0],
marker=dict(colors=colors, line=dict(color='#FFFFFF', width=2)))
# Plot histogram based on rolling 2 dice
dice_1 = np.random.randint(1,7,5000)
dice_2 = np.random.randint(1,7,5000)
dice_sum = dice_1 + dice_2
# bins represent the number of bars to make
# Can define x label, color, title
# marginal creates another plot (violin, box, rug)
fig = px.histogram(dice_sum, nbins=11, labels={'value':'Dice Roll'},
title='5000 Dice Roll Histogram', marginal='violin',
color_discrete_sequence=['green'])
fig.update_layout(
xaxis_title_text='Dice Roll',
yaxis_title_text='Dice Sum',
bargap=0.2, showlegend=False
)
# Stack histograms based on different column data
df_tips = px.data.tips()
df_tips.head()
total_bill | tip | sex | smoker | day | time | size | |
---|---|---|---|---|---|---|---|
0 | 16.99 | 1.01 | Female | No | Sun | Dinner | 2 |
1 | 10.34 | 1.66 | Male | No | Sun | Dinner | 3 |
2 | 21.01 | 3.50 | Male | No | Sun | Dinner | 3 |
3 | 23.68 | 3.31 | Male | No | Sun | Dinner | 2 |
4 | 24.59 | 3.61 | Female | No | Sun | Dinner | 4 |
px.histogram(df_tips, x="total_bill", color="sex")
# A box plot allows you to compare different variables
# The box shows the quartiles of the data. The bar in the middle is the median
# The whiskers extend to all the other data aside from the points that are considered
# to be outliers
df_tips = px.data.tips()
df_tips.head()
total_bill | tip | sex | smoker | day | time | size | |
---|---|---|---|---|---|---|---|
0 | 16.99 | 1.01 | Female | No | Sun | Dinner | 2 |
1 | 10.34 | 1.66 | Male | No | Sun | Dinner | 3 |
2 | 21.01 | 3.50 | Male | No | Sun | Dinner | 3 |
3 | 23.68 | 3.31 | Male | No | Sun | Dinner | 2 |
4 | 24.59 | 3.61 | Female | No | Sun | Dinner | 4 |
# We can see which sex tips the most, points displays all the data points
px.box(df_tips, x='sex', y='tip', points='all')
# Display tip sex data by day
px.box(df_tips, x='day', y='tip', color='sex')
# Adding standard deviation and mean
fig = go.Figure()
fig.add_trace(go.Box(x=df_tips.sex, y=df_tips.tip, marker_color='blue',
boxmean='sd'))
# Complex Styling
df_stocks = px.data.stocks()
df_stocks.head()
date | GOOG | AAPL | AMZN | FB | NFLX | MSFT | |
---|---|---|---|---|---|---|---|
0 | 2018-01-01 | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 |
1 | 2018-01-08 | 1.018172 | 1.011943 | 1.061881 | 0.959968 | 1.053526 | 1.015988 |
2 | 2018-01-15 | 1.032008 | 1.019771 | 1.053240 | 0.970243 | 1.049860 | 1.020524 |
3 | 2018-01-22 | 1.066783 | 0.980057 | 1.140676 | 1.016858 | 1.307681 | 1.066561 |
4 | 2018-01-29 | 1.008773 | 0.917143 | 1.163374 | 1.018357 | 1.273537 | 1.040708 |
fig = go.Figure()
# Show all points, spread them so they don't overlap and change whisker width
fig.add_trace(go.Box(y=df_stocks.GOOG, boxpoints='all', name='Google',
fillcolor='blue', jitter=0.5, whiskerwidth=0.2))
fig.add_trace(go.Box(y=df_stocks.AAPL, boxpoints='all', name='Apple',
fillcolor='red', jitter=0.5, whiskerwidth=0.2))
# Change background / grid colors
fig.update_layout(title='Google vs. Apple',
yaxis=dict(gridcolor='rgb(255, 255, 255)',
gridwidth=3),
paper_bgcolor='rgb(243, 243, 243)',
plot_bgcolor='rgb(243, 243, 243)')
# Violin Plot is a combination of the boxplot and KDE
# While a box plot corresponds to data points, the violin plot uses the KDE estimation
# of the data points
df_tips = px.data.tips()
df_tips.head()
total_bill | tip | sex | smoker | day | time | size | |
---|---|---|---|---|---|---|---|
0 | 16.99 | 1.01 | Female | No | Sun | Dinner | 2 |
1 | 10.34 | 1.66 | Male | No | Sun | Dinner | 3 |
2 | 21.01 | 3.50 | Male | No | Sun | Dinner | 3 |
3 | 23.68 | 3.31 | Male | No | Sun | Dinner | 2 |
4 | 24.59 | 3.61 | Female | No | Sun | Dinner | 4 |
px.violin(df_tips, y="total_bill", box=True, points='all')
# Multiple plots
px.violin(df_tips, y="tip", x="smoker", color="sex", box=True, points="all",
hover_data=df_tips.columns)
# Morph left and right sides based on if the customer smokes
fig = go.Figure()
fig.add_trace(go.Violin(x=df_tips['day'][ df_tips['smoker'] == 'Yes' ],
y=df_tips['total_bill'][ df_tips['smoker'] == 'Yes' ],
legendgroup='Yes', scalegroup='Yes', name='Yes',
side='negative',
line_color='blue'))
fig.add_trace(go.Violin(x=df_tips['day'][ df_tips['smoker'] == 'No' ],
y=df_tips['total_bill'][ df_tips['smoker'] == 'No' ],
legendgroup='Yes', scalegroup='Yes', name='No',
side='positive',
line_color='red'))
# Create a heatmap using Seaborn data
flights = sns.load_dataset("flights")
flights.head()
year | month | passengers | |
---|---|---|---|
0 | 1949 | Jan | 112 |
1 | 1949 | Feb | 118 |
2 | 1949 | Mar | 132 |
3 | 1949 | Apr | 129 |
4 | 1949 | May | 121 |
# You can set bins with nbinsx and nbinsy
fig = px.density_heatmap(flights, x='year', y='month', z='passengers',
color_continuous_scale="Viridis")
fig
# You can add histograms
fig = px.density_heatmap(flights, x='year', y='month', z='passengers',
marginal_x="histogram", marginal_y="histogram")
fig
# Create a 3D scatter plot using flight data
fig = px.scatter_3d(flights, x='year', y='month', z='passengers', color='year',
opacity=0.7, width=800, height=400)
fig
fig = px.line_3d(flights, x='year', y='month', z='passengers', color='year')
fig
# With a scatter matrix we can compare changes when comparing column data
fig = px.scatter_matrix(flights, color='month')
fig
# There are many interesting ways of working with maps
# plotly.com/python-api-reference/generated/plotly.express.scatter_geo.html
df = px.data.gapminder().query("year == 2007")
fig = px.scatter_geo(df, locations="iso_alpha",
color="continent", # which column to use to set the color of markers
hover_name="country", # column added to hover information
size="pop", # size of markers
projection="orthographic")
fig
# You can color complex maps like we do here representing unemployment data
# Allows us to grab data from a supplied URL
from urllib.request import urlopen
# Used to decode JSON data
import json
# Grab US county geometry data
with urlopen('https://raw.githubusercontent.com/plotly/datasets/master/geojson-counties-fips.json') as response:
counties = json.load(response)
# Grab unemployment data based on each counties Federal Information Processing number
df = pd.read_csv("https://raw.githubusercontent.com/plotly/datasets/master/fips-unemp-16.csv",
dtype={"fips": str})
df.head()
--------------------------------------------------------------------------- FileNotFoundError Traceback (most recent call last) D:\software_install\anaconda\lib\urllib\request.py in do_open(self, http_class, req, **http_conn_args) 1349 try: -> 1350 h.request(req.get_method(), req.selector, req.data, headers, 1351 encode_chunked=req.has_header('Transfer-encoding')) D:\software_install\anaconda\lib\http\client.py in request(self, method, url, body, headers, encode_chunked) 1254 """Send a complete request to the server.""" -> 1255 self._send_request(method, url, body, headers, encode_chunked) 1256 D:\software_install\anaconda\lib\http\client.py in _send_request(self, method, url, body, headers, encode_chunked) 1300 body = _encode(body, 'body') -> 1301 self.endheaders(body, encode_chunked=encode_chunked) 1302 D:\software_install\anaconda\lib\http\client.py in endheaders(self, message_body, encode_chunked) 1249 raise CannotSendHeader() -> 1250 self._send_output(message_body, encode_chunked=encode_chunked) 1251 D:\software_install\anaconda\lib\http\client.py in _send_output(self, message_body, encode_chunked) 1009 del self._buffer[:] -> 1010 self.send(msg) 1011 D:\software_install\anaconda\lib\http\client.py in send(self, data) 949 if self.auto_open: --> 950 self.connect() 951 else: D:\software_install\anaconda\lib\http\client.py in connect(self) 1423 -> 1424 self.sock = self._context.wrap_socket(self.sock, 1425 server_hostname=server_hostname) D:\software_install\anaconda\lib\ssl.py in wrap_socket(self, sock, server_side, do_handshake_on_connect, suppress_ragged_eofs, server_hostname, session) 499 # ctx._wrap_socket() --> 500 return self.sslsocket_class._create( 501 sock=sock, D:\software_install\anaconda\lib\ssl.py in _create(cls, sock, server_side, do_handshake_on_connect, suppress_ragged_eofs, server_hostname, context, session) 1039 raise ValueError("do_handshake_on_connect should not be specified for non-blocking sockets") -> 1040 self.do_handshake() 1041 except (OSError, ValueError): D:\software_install\anaconda\lib\ssl.py in do_handshake(self, block) 1308 self.settimeout(None) -> 1309 self._sslobj.do_handshake() 1310 finally: FileNotFoundError: [Errno 2] No such file or directory During handling of the above exception, another exception occurred: URLError Traceback (most recent call last) <ipython-input-49-c9555274605d> in <module> 6 import json 7 # Grab US county geometry data ----> 8 with urlopen('https://raw.githubusercontent.com/plotly/datasets/master/geojson-counties-fips.json') as response: 9 counties = json.load(response) 10 D:\software_install\anaconda\lib\urllib\request.py in urlopen(url, data, timeout, cafile, capath, cadefault, context) 220 else: 221 opener = _opener --> 222 return opener.open(url, data, timeout) 223 224 def install_opener(opener): D:\software_install\anaconda\lib\urllib\request.py in open(self, fullurl, data, timeout) 523 524 sys.audit('urllib.Request', req.full_url, req.data, req.headers, req.get_method()) --> 525 response = self._open(req, data) 526 527 # post-process response D:\software_install\anaconda\lib\urllib\request.py in _open(self, req, data) 540 541 protocol = req.type --> 542 result = self._call_chain(self.handle_open, protocol, protocol + 543 '_open', req) 544 if result: D:\software_install\anaconda\lib\urllib\request.py in _call_chain(self, chain, kind, meth_name, *args) 500 for handler in handlers: 501 func = getattr(handler, meth_name) --> 502 result = func(*args) 503 if result is not None: 504 return result D:\software_install\anaconda\lib\urllib\request.py in https_open(self, req) 1391 1392 def https_open(self, req): -> 1393 return self.do_open(http.client.HTTPSConnection, req, 1394 context=self._context, check_hostname=self._check_hostname) 1395 D:\software_install\anaconda\lib\urllib\request.py in do_open(self, http_class, req, **http_conn_args) 1351 encode_chunked=req.has_header('Transfer-encoding')) 1352 except OSError as err: # timeout error -> 1353 raise URLError(err) 1354 r = h.getresponse() 1355 except: URLError: <urlopen error [Errno 2] No such file or directory>
# Draw map using the county JSON data, color using unemployment values on a range of 12
fig = px.choropleth(df, geojson=counties, locations='fips', color='unemp',
color_continuous_scale="Viridis",
range_color=(0, 12),
scope="usa",
labels={'unemp':'unemployment rate'}
)
fig
# Polar charts display data radially
# Let's plot wind data based on direction and frequency
# You can change size and auto-generate different symbols as well
df_wind = px.data.wind()
df_wind.head()
direction | strength | frequency | |
---|---|---|---|
0 | N | 0-1 | 0.5 |
1 | NNE | 0-1 | 0.6 |
2 | NE | 0-1 | 0.5 |
3 | ENE | 0-1 | 0.4 |
4 | E | 0-1 | 0.4 |
px.scatter_polar(df_wind, r="frequency", theta="direction", color="strength",
size="frequency", symbol="strength")
# Data can also be plotted using lines radially
# A template makes the data easier to see
px.line_polar(df_wind, r="frequency", theta="direction", color="strength",
line_close=True, template="plotly_dark", width=800, height=400)
# Used to represent ratios of 3 variables
df_exp = px.data.experiment()
df_exp.head()
experiment_1 | experiment_2 | experiment_3 | gender | group | |
---|---|---|---|---|---|
0 | 96.876065 | 93.417942 | 73.033193 | male | control |
1 | 87.301336 | 129.603395 | 66.056554 | female | control |
2 | 97.691312 | 106.187916 | 103.422709 | male | treatment |
3 | 102.978152 | 93.814682 | 56.995870 | female | treatment |
4 | 87.106993 | 107.019985 | 72.140292 | male | control |
px.scatter_ternary(df_exp, a="experiment_1", b="experiment_2",
c='experiment_3', hover_name="group", color="gender")
# You can create numerous subplots
df_tips = px.data.tips()
df_tips.head()
total_bill | tip | sex | smoker | day | time | size | |
---|---|---|---|---|---|---|---|
0 | 16.99 | 1.01 | Female | No | Sun | Dinner | 2 |
1 | 10.34 | 1.66 | Male | No | Sun | Dinner | 3 |
2 | 21.01 | 3.50 | Male | No | Sun | Dinner | 3 |
3 | 23.68 | 3.31 | Male | No | Sun | Dinner | 2 |
4 | 24.59 | 3.61 | Female | No | Sun | Dinner | 4 |
px.scatter(df_tips, x="total_bill", y="tip", color="smoker", facet_col="sex")
# We can line up data in rows and columns
px.histogram(df_tips, x="total_bill", y="tip", color="sex", facet_row="time", facet_col="day",
category_orders={"day": ["Thur", "Fri", "Sat", "Sun"], "time": ["Lunch", "Dinner"]})
# This dataframe provides scores for different students based on the level
# of attention they could provide during testing
att_df = sns.load_dataset("attention")
att_df.head()
Unnamed: 0 | subject | attention | solutions | score | |
---|---|---|---|---|---|
0 | 0 | 1 | divided | 1 | 2.0 |
1 | 1 | 2 | divided | 1 | 3.0 |
2 | 2 | 3 | divided | 1 | 3.0 |
3 | 3 | 4 | divided | 1 | 5.0 |
4 | 4 | 5 | divided | 1 | 4.0 |
fig = px.line(att_df, x='solutions', y='score', facet_col='subject',
facet_col_wrap=5, title='Scores Based on Attention')
fig
# Create an animated plot that you can use to cycle through continent
# GDP & life expectancy changes
df_cnt = px.data.gapminder()
df_cnt.head()
country | continent | year | lifeExp | pop | gdpPercap | iso_alpha | iso_num | |
---|---|---|---|---|---|---|---|---|
0 | Afghanistan | Asia | 1952 | 28.801 | 8425333 | 779.445314 | AFG | 4 |
1 | Afghanistan | Asia | 1957 | 30.332 | 9240934 | 820.853030 | AFG | 4 |
2 | Afghanistan | Asia | 1962 | 31.997 | 10267083 | 853.100710 | AFG | 4 |
3 | Afghanistan | Asia | 1967 | 34.020 | 11537966 | 836.197138 | AFG | 4 |
4 | Afghanistan | Asia | 1972 | 36.088 | 13079460 | 739.981106 | AFG | 4 |
px.scatter(df_cnt, x="gdpPercap", y="lifeExp", animation_frame="year",
animation_group="country",
size="pop", color="continent", hover_name="country",
log_x=True, size_max=55, range_x=[100,100000], range_y=[25,90])
# Watch as bars chart population changes
px.bar(df_cnt, x="continent", y="pop", color="continent",
animation_frame="year", animation_group="country", range_y=[0,4000000000])